#pragma once
#include <string>
#include <functional>
#include <workflow/WFTaskFactory.h>
#include <message/frpc_msgio.h>
#include <cppbase/utility.h>
#include <google/protobuf/message.h>
#include "frpc_context.h"
namespace frpc {

	
	struct ClientOptions
	{
		int send_timeout;
		int receive_timeout;
		int wait_timeout;
		int keep_alive_timeout;
		int retry_max;
		CompressType compress_type;	//RPCCompressType
		ClientOptions() {
			send_timeout = 3000;
			receive_timeout = 3000;
			wait_timeout = 3000;
			keep_alive_timeout = 60*30 * 1000;//30 min keep alive
			retry_max = 0;
			compress_type = CompressType::None;
		}
	};

	struct RPCTaskParams
	{
		int send_timeout;
		int receive_timeout;
		int watch_timeout;
		int keep_alive_timeout;
		int retry_max;
		int compress_type;	//RPCCompressType
	//	int data_type;		//RPCDataType
	};

	struct RPCClientParams
	{
		RPCTaskParams task_params;
		//host + port + is_ssl
		std::string host;
		unsigned short port;
		bool is_ssl;
		//or URL
		std::string url;
	};



	static constexpr struct RPCTaskParams RPC_TASK_PARAMS_DEFAULT =
	{
		/*	.send_timeout		=	*/	3*1000,
		/*	.receive_timeout	=	*/	3*1000,
		/*	.watch_timeout		=	*/	0,
		/*	.keep_alive_timeout	=	*/	30 * 1000,
		/*	.retry_max			=	*/	0,
		/*	.compress_type		=	*/	int(CompressType::None),
	//	/*	.data_type			=	*/	RPCDataUndefined
	};

	static const struct RPCClientParams RPC_CLIENT_PARAMS_DEFAULT =
	{
		/*	.task_params		=	*/	RPC_TASK_PARAMS_DEFAULT,
		/*	.host				=	*/	"",
		/*	.port				=	*/	8000,
		/*	.is_ssl				=	*/	false,
		/*	.url				=	*/	""
	};

	class RPCWorker
	{
	public:
		RPCWorker(Context* ctx, Request* req, Reply* resp)
		{
			this->ctx = ctx;
			this->req = req;
			this->resp = resp;
			//this->__server_serialize = NULL;
		}

		~RPCWorker()
		{
			delete this->ctx;
			delete this->pb_input;
			delete this->pb_output;

		}

	public:
		Context* ctx;
		Request* req;
		Reply* resp;

	private:
		int resp_serialize_pb()
		{
			return this->resp->serialize(this->pb_output);
		}


		//int (RPCWorker::* __server_serialize)();
		google::protobuf::Message* pb_input = nullptr;
		google::protobuf::Message* pb_output = nullptr;

	};



	template<class OUTPUT>
	static inline int
		ClientRPCDoneImpl(int status_code,
			RPCWorker& worker,
			const std::function<void(OUTPUT*, Context*)>& rpc_done)
	{
		if (status_code == 0)
		{
			OUTPUT out;

			status_code = worker.resp->deserialize(&out);
			if (status_code == 0)
				rpc_done(&out, worker.ctx);

			return status_code;
		}

		rpc_done(NULL, worker.ctx);
		return status_code;
	}


	class ClientTask : public WFComplexClientTask<Request, Reply>
	{
		ClientOptions _opt;
	protected:
		virtual bool check_request();//called by parent,task start when it returns true
		virtual CommMessageOut* message_out();
		virtual CommMessageIn* message_in();
		void set_compress_type(CompressType type);
		//int body_to_bytes(const google::protobuf::Message* pb);
		//int body_from_bytes(const char* buf, int len);
		virtual int keep_alive_timeout();
		//	virtual bool init_success(); caled by init_with url(ip)
		virtual bool finish_once();//called in parent::done() when a req-reply done.return true means ready for user cmd.
		void rpc_callback(WFNetworkTask<Request, Reply>* task);

	private:

		bool _is_user_request;
		int _state;
		std::function<int(int, RPCWorker&)> _user_done;
		//std::string _service_name;
		//std::string _method_name;
	public:
		bool get_remote(std::string& ip, unsigned short* port) const;
		virtual WFConnection* get_connection() const
		{
			CommConnection* conn;

			if (this->target)
			{
				conn = this->CommSession::get_connection();
				if (conn)
					return (WFConnection*)conn;
			}

			errno = ENOTCONN;
			return NULL;
		}
		//static void set_ip_port(std::string host,uint16_t port){	}
		//static New()
		ClientTask(const std::string& service_name,
			const std::string& method_name, RPCTaskParams* opt, std::function<int(int, RPCWorker&)>&& callback);
	};


	class Client {

	protected:
		std::string service_name;
		RPCClientParams params;
		ParsedURI uri;
		struct sockaddr_storage ss;
		socklen_t ss_len;
		bool _has_addr_info;
		void task_init(ClientTask*) ;
		void init(const RPCClientParams* params);
	protected:
		template<class OUTPUT>
		ClientTask* create_rpc_client_task(const std::string& method_name,
			std::function<void(OUTPUT*, Context*)>&& done)
		{

			auto* task = new ClientTask(this->service_name,
				method_name,
				&this->params.task_params,
				[done](int status_code, RPCWorker& worker) -> int {
					return ClientRPCDoneImpl(status_code, worker, done);
				});

			this->task_init(task);

			return task;
		}
	public:
		Client(const char* service_name_):service_name(service_name_),params(RPC_CLIENT_PARAMS_DEFAULT), _has_addr_info(false){}
	};
}